package hichecker.controller;

import hichecker.tools.event.HiCheckerEvent;
import hichecker.tools.event.HiCheckerEvent.EventType;
import hichecker.tools.event.abstract_event.AbstractObservable;
import hichecker.tools.event.abstract_event.Observable;
import hichecker.tools.event.abstract_event.Observer;
import hichecker.tools.export.CSVFile;
import hichecker.tools.export.CSVWriter;
import hichecker.tools.export.XMLWriter;
import hichecker.university.Course;
import hichecker.university.ImportExportCourse;
import hichecker.university.PTL;
import hichecker.university.PTLSeries;
import hichecker.university.PVL;
import hichecker.university.PVLSeries;
import hichecker.university.Requirement;
import hichecker.university.SemesterDate;
import hichecker.university.Series;
import hichecker.university.Student;
import hichecker.university.UniversityPart;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import org.apache.log4j.Logger;

/**
 * Class that handles the controlls between the user interface and the underlying data structure.
 */
public class MainController extends AbstractObservable<HiCheckerEvent> implements
Observer<HiCheckerEvent> {
	private Logger logger = Logger.getLogger(MainController.class);

	private Course course;
	private File courseFile;
	private UniversityPart viewedElement;
	private boolean fullAccess;
	private boolean showConfig;

	/**
	 * Creates the controller and adds himself to his observers
	 *
	 * @param hasFullAccess
	 *            indicats whether the programm should enable all functions
	 */
	public MainController(boolean hasFullAccess) {
		this.fullAccess = hasFullAccess;
		this.showConfig = false;
		addObserver(this);
	}

	/**
	 * Creates a new course with default values.
	 */
	public void createNewCourse() {
		course = new Course();
		courseFile = null;
		fireEvent(new HiCheckerEvent(EventType.UPDATE));
	}

	public boolean courseLoaded() {
		return course != null;
	}

	/**
	 * @return whether the programm was started with full functionality or not
	 */
	public boolean hasFullAccess() {
		return fullAccess;
	}

	/**
	 * @return the currently opende course, may be {@code null}
	 */
	public Course getCourse() {
		return course;
	}

	/**
	 * @return a list of all students participating in this course
	 */
	public List<Student> getStudents() {
		return course.getParticipants();
	}


	public void fireItemUpdate() {
		fireEvent(new HiCheckerEvent(EventType.UPDATE_ITEM_NAME));
	}

	/**
	 * @param name the name to set for the opened course
	 */
	public void setCourseName(String name) {
		course.setName(name);
		fireItemUpdate();
	}

	/**
	 * @param courseID the id to set for the opened course
	 */
	public void setCourseID(UUID courseID) {
		course.setID(courseID);
	}

	/**
	 * @param number the number to set for the opened course
	 */
	public void setCourseNumber(int number) {
		course.setNumber(number);
	}

	/**
	 * @param lecturer the name of the lecturer to set for the opened course
	 */
	public void setCourseLecturer(String lecturer) {
		course.setLecturer(lecturer);
	}

	/**
	 * @param term
	 *            the term to set for the opened semester, has to have the format: "WS YY/YY" or
	 *            "SS YY"
	 */
	public void setCourseTerm(String term) {
		course.setTerm(SemesterDate.parseSemesterDate(term));
		fireItemUpdate();
	}


	/**
	 * Returns the suggested filename for the save dialog. Based on the name and term of the course
	 * and the name of the series if {@code withSeries} is true
	 *
	 * @param withSeries
	 *            if true the series name is add to the filename
	 * @return
	 */
	public String getSuggestedFileName(boolean withSeries) {
		String courseName = course.getName() + " "
		+ (course.getTerm() == null ? "" : course.getTerm().fitToFileName());
		return courseName + (withSeries ? " - " + viewedElement.getName() : "");
	}

	/**
	 * Saves the currently opened course into a file.
	 *
	 * @param filePath
	 *            the path to save the file
	 */
	public void saveNewFile(File file) {
		this.courseFile = file;
		saveFile();
	}

	/**
	 * Saves the course at the currently opened filepath
	 */
	public void saveFile() {
		if (courseFile == null) {
			logger.error("There is no filepath to save!");
			return;
		}

		// create main file
		XMLWriter.saveToXML(courseFile, course);

		// create backup folder
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");

		String backupFolder = courseFile.getName().replaceFirst("\\." + Config.FILE_EXT, "")
				.replaceFirst("\\." + Config.FILE_EXT_PART, "") + " [BACKUP]";
		String backupPath = courseFile.getParent() + "/" + backupFolder + "/"
				+ format.format(new Date()) + "/";
		new File(backupPath).mkdirs();

		// create xml backup
		String xmlBackupFile = "[" + Config.FILE_EXT.toUpperCase() + "] " + courseFile.getName();
		XMLWriter.saveToXML(new File(backupPath + xmlBackupFile), course);

		// create csv backup
		List<CSVFile> files = course.getRepresentation(false, this).toCSV("");
		for (CSVFile file : files)
			CSVWriter.writeToFile(file, backupPath);
	}

	/**
	 * Loads a course out of a filepath
	 * @param filePath
	 */
	public void openFile(File file) {
		if (fullAccess)
			course = (Course) XMLWriter.loadFromXML(file, Course.class);
		else
			course = (ImportExportCourse) XMLWriter.loadFromXML(file,
					ImportExportCourse.class);
		// There was no course loaded
		if (course == null)
			return;

		this.courseFile = file;

		fireEvent(new HiCheckerEvent(EventType.UPDATE));
	}

	/**
	 * Closes the opened course by resetting it and the filepath
	 */
	public void closeFile() {
		course = null;
		courseFile = null;
		viewedElement = null;

		fireEvent(new HiCheckerEvent(EventType.SET_VIEWED_ELEMENT));
	}

	public void closeImportFile(ImportExportCourse importedCourse) {
		course.removeImportCourse(importedCourse);
		fireEvent(HiCheckerEvent.newMenuContent(course, null));
	}

	/**
	 * Exports the currently {@link #getViewedElement() viewedElement} into a file. Has to be a
	 * {@link Series series}.
	 *
	 * @param filePath
	 */
	public void exportFile(File file) {
		if (viewedElement == null || viewedElement.getClass().getSuperclass() != Series.class) {
			logger.error("Can't open export dialog, no series selected.");
			return;
		}
		Series series = (Series) viewedElement;

		// Create a copy of the course
		ImportExportCourse exportCourse = new ImportExportCourse(course);

		// Check which requirement has the series to export
		Requirement exportElement = null;
		for (Requirement req : exportCourse.getRequirements()) {
			// PVL
			if (req instanceof PVL) {
				PVL pvl = (PVL) req;
				if (pvl.containsSeries(series)) {
					pvl.clearSeries();
					pvl.addSeries((PVLSeries) series);
					exportElement = pvl;
					break;
				}
			}
			// PTL
			if (req instanceof PTL) {
				PTL ptl = (PTL) req;
				if (ptl.containsSeries(series)) {
					ptl.clearSeries();
					ptl.addSeries((PTLSeries) series);
					exportElement = ptl;
					break;
				}
			}
		}
		if (exportElement == null) {
			logger.error("Export failed, series not found.");
			return;
		}

		// Clear course, add found requirement
		exportCourse.clearRequirements();
		exportCourse.addRequirement(exportElement);

		// Save course
		XMLWriter.saveToXML(file, exportCourse);

	}

	/**
	 * Imports the given part file and add all information to return it to the view.
	 *
	 * @param filePath
	 */
	public void importFile(File file) {
		// Load course and check ids
		ImportExportCourse importedCourse = (ImportExportCourse) XMLWriter.loadFromXML(file, ImportExportCourse.class);
		if (!(importedCourse.getID().equals(course.getID()))) {
			logger.warn("Wrong import file. The imported course id doesn't match the opened course id");
			return;
		}

		// Extract series and check ids
		Series importedSeries = importedCourse.getTheOnlySeries();
		Series selectedSeries = course.getSeriesByID(importedSeries.getID());
		if (selectedSeries == null) {
			logger.warn("Wrong import file. A series with the imported id doesn't exist in the opened course");
			return;
		}

		course.addImportCourse(new ImportExportCourse(importedCourse));
		fireEvent(HiCheckerEvent.newMenuContent(course, importedCourse.getID()));
	}

	public File getCourseFile() {
		return courseFile;
	}

	public void setCourseFile(File courseFile) {
		this.courseFile = courseFile;
	}

	/**
	 * Switches view between data- and settingsview
	 */
	public void switchShowConfig() {
		showConfig = !showConfig;
		fireEvent(new HiCheckerEvent(EventType.SWITCH));
	}

	public boolean isShowConfig() {
		return showConfig;
	}

	public void setShowConfig(boolean showConfig) {
		this.showConfig = showConfig;
	}

	/**
	 * @return UniversityPart the currently viewed element
	 */
	public UniversityPart getViewedElement() {
		return viewedElement;
	}

	/**
	 * @param viewedElemnt the currently viewed element to set
	 */
	public void setViewedElement(UniversityPart viewedElemnt) {
		this.viewedElement = viewedElemnt;
		fireEvent(new HiCheckerEvent(EventType.SET_VIEWED_ELEMENT));
	}

	/**
	 * Fügt zu einem UniversityPart ein Unterelement hinzu.
	 *
	 * @param type
	 *            Spezifiziert das Unterelement. Alle verfügbaren Unterelemente können mit
	 *            UniversityPart.getSubItems() erfragt werden.
	 * @param item
	 *            Oberelement
	 */
	public void addSubItem(int type, UniversityPart item) {
		UUID id = item.addSubPart(type);
		setShowConfig(true);
		fireEvent(HiCheckerEvent.newMenuContent(item, id));
	}

	public void removeSubItem(UUID id, UniversityPart item) {
		item.removeSubPart(id);
		fireEvent(HiCheckerEvent.newMenuContent(item, null));
	}

	@Override
	public void notify(Observable<? extends HiCheckerEvent> sender, HiCheckerEvent event) {
		logger.debug(event);
	}
}
